#!/bin/bash
# $Id: twiping.in 37 2014-12-09 14:42:07Z archie.cobbs $

# Bail on error
set -e

# Constants
DEFAULT_CONFIG_FILE='@defaultconf@'
BASE_URL='https://api.twilio.com/'
ACCOUNTS_PATH='/2010-04-01/Accounts/${ACCOUNT_SID}/Messages'
RESULT_XSL='@pkgdatadir@/result.xsl'
TWIPING_XSL='@pkgdatadir@/twiping.xsl'
SLEEP_INTERVAL='3'
CURL="@CURL@"
SED="@SED@"
GREP="@GREP@"
EXPR="@EXPR@"
SED_EXTENDED_REGEX_FLAG="@SED_EXTENDED_REGEX_FLAG@"
DATE="@DATE@"
SLEEP="@SLEEP@"
HEXDUMP="@HEXDUMP@"
XSLTPROC="@XSLTPROC@"
MAX_WAIT_TIME="30"
PAGE_SIZE="500"

# Usage message
usage()
{
    echo "Usage: twiping [-c config.txt] [-C curlflag] [-F from-number] [-t testcode] [-w timeout] number" 1>&2
    echo "Options:" 1>&2
    echo "    -C    Pass curlflag to curl(1)" 1>&2
    echo "    -c    Specify config file (default \"${DEFAULT_CONFIG_FILE}\")" 1>&2
    echo "    -F    Specify sending phone number (10 digits)" 1>&2
    echo "    -t    Assume outgoing SMS message with \`testcode' has already been sent" 1>&2
    echo "    -w    Specify response timeout in seconds (default ${MAX_WAIT_TIME})" 1>&2
}

# Function to normalize a phone number to the way Twilio likes it
normalize()
{
    echo ${1+"$@"} | sed -r \
      -e 's/[^0-9]//g' \
      -e 's/^1?([0-9]{10})$/+1\1/g'
}

# Parse flags passed in on the command line
CONFIG_FILE="${DEFAULT_CONFIG_FILE}"
TRUNCATE_LIMIT="${DEFAULT_TRUNCATE_LIMIT}"
FROM_NUMBER=""
CURL_FLAGS=""
TESTCODE=""
while [ ${#} -gt 0 ]; do
    case "$1" in
        -c)
            shift
            CONFIG_FILE="${1}"
            shift
            ;;
        -C)
            shift
            CURL_FLAGS="${CURL_FLAGS} ${1}"
            shift
            ;;
        -F)
            shift
            FROM_NUMBER="${1}"
            shift
            ;;
        -t)
            shift
            TESTCODE="${1}"
            shift
            ;;
        -w)
            shift
            MAX_WAIT_TIME="${1}"
            shift
            ;;
        -h|--help)
            usage
            exit
            ;;
        --)
            shift
            break
            ;;
        *)
            break
            ;;
    esac
done
case "${#}" in
    1)
        TO_NUMBER="$1"
        ;;
    *)
        usage
        exit 1
        ;;
esac

# See if config file is readable
if ! test -r "${CONFIG_FILE}"; then
    echo "twiping: can't read ${CONFIG_FILE}" 1>&2
    exit 1
fi

# Parse config file
. "${CONFIG_FILE}"

# Normalize numbers
FROM_NUMBER=`normalize "${FROM_NUMBER}"`
TO_NUMBER=`normalize "${TO_NUMBER}"`

# Sanity check stuff
if ! [[ "${FROM_NUMBER}" =~ ^(\+1[2-9][0-9]{2}[2-9][0-9]{2}[0-9]{4}|[0-9]{5,6})$ ]]; then
    echo "twiping: invalid source phone number \`${FROM_NUMBER}'" 1>&2
    exit 1
fi
if ! [[ "${TO_NUMBER}" =~ ^(\+1[2-9][0-9]{2}[2-9][0-9]{2}[0-9]{4}|[0-9]{5,6})$ ]]; then
    echo "twiping: invalid destination phone number \`${TO_NUMBER}'" 1>&2
    exit 1
fi
if ! [[ "${ACCOUNT_SID}" =~ ^AC[0-9a-f]{32}$ ]]; then
    echo "twiping: invalid account SID \`${ACCOUNT_SID}'" 1>&2
    exit 1
fi
if ! [[ "${AUTH_TOKEN}" =~ ^[0-9a-f]{32}$ ]]; then
    echo "twiping: invalid authentication token" 1>&2
    exit 1
fi

# Build URL
URL="`echo ${BASE_URL} | sed 's|/$||g'`""`eval echo ${ACCOUNTS_PATH}`"

# Create temporary files for message response and error
RESPONSE_FILE=`mktemp -q /tmp/twiping.XXXXXX`
if [ $? -ne 0 ]; then
    echo "twiping: can't create temporary file" 1>&2
    exit 1
fi
ERROR_FILE=`mktemp -q /tmp/twiping.XXXXXX`
if [ $? -ne 0 ]; then
    rm -f ${RESPONSE_FILE}
    echo "twiping: can't create temporary file" 1>&2
    exit 1
fi
trap "rm -f ${RESPONSE_FILE} ${ERROR_FILE}" 0 2 3 5 10 13 15

# Generate and send test code if not already sent
if [ -z "${TESTCODE}" ]; then

    # Generate random test string
    TESTCODE="TEST`${HEXDUMP} -n 4 -v -e '1/1 "%02x"' /dev/urandom`"

    # Send test code to Twilio
    echo "${TESTCODE}" \
      | "${CURL}" --silent \
      --user "${ACCOUNT_SID}:${AUTH_TOKEN}" \
      --data "From=${FROM_NUMBER}" \
      --data "To=${TO_NUMBER}" \
      --data-urlencode "Body@-" \
      --output "${RESPONSE_FILE}" \
      ${CURL_FLAGS} \
      "${URL}"
fi

# Check result
CURL_RESULT="$?"
if [ "${CURL_RESULT}" -ne 0 ]; then
    echo "twiping: error sending request (curl returned ${CURL_RESULT})" 1>&2
    exit 1
fi

# Apply XSLT to returned XML to get error message
"${XSLTPROC}" "${RESULT_XSL}" "${RESPONSE_FILE}" > "${ERROR_FILE}" 2>&1
if [ $? -ne 0 ]; then
    echo -n "twiping: error parsing result:" 1>&2
    cat "${ERROR_FILE}" 1>&2
    exit 1
fi

# Was there an error returned?
if [ -s "${ERROR_FILE}" ]; then
    echo -n "twiping: " 1>&2
    cat "${ERROR_FILE}" 1>&2
    exit 1
fi

# Wait for response
START_TIME=`"${DATE}" -u +%s`
RESPONSE_PATTERN='^(SM[[:xdigit:]]{32})/([0-9+]+)/([0-9+]+)/([^/]+)/(.*)$'
while true; do

    # Check elapsed time
    CURRENT_TIME=`"${DATE}" -u +%s`
    ELAPSED_TIME=`"${EXPR}" "${CURRENT_TIME}" - "${START_TIME}" || true`
    if [ "${ELAPSED_TIME}" -ge "${MAX_WAIT_TIME}" ]; then
        echo "smsping: no response after ${ELAPSED_TIME} seconds" 1>&2
        exit 2
    fi

    # Query messages
    "${CURL}" \
      --silent --compressed \
      --user "${ACCOUNT_SID}:${AUTH_TOKEN}" \
      --output "${RESPONSE_FILE}" \
      ${CURL_FLAGS} \
      "${URL}?PageSize=${PAGE_SIZE}"

    # Check result
    CURL_RESULT="$?"
    if [ "${CURL_RESULT}" -ne 0 ]; then
        echo "smsping: error retrieving messages (curl returned ${CURL_RESULT})" 1>&2
        exit 1
    fi

    # Update elapsed time
    CURRENT_TIME=`"${DATE}" -u +%s`
    ELAPSED_TIME=`"${EXPR}" ${CURRENT_TIME} - ${START_TIME} || true`

    # Apply XSLT to returned XML to extract incoming messages
    "${XSLTPROC}" "${TWIPING_XSL}" "${RESPONSE_FILE}" > "${ERROR_FILE}" 2>&1
    if [ $? -ne 0 ]; then
        echo -n "twiping: error parsing messages: " 1>&2
        cat "${ERROR_FILE}" 1>&2
        exit 1
    fi

    # Find reply containing our test code detected, if any
    if "${GREP}" -Fq "${TESTCODE}" "${ERROR_FILE}"; then
        "${GREP}" -F "${TESTCODE}" "${ERROR_FILE}" \
          | "${SED}" -n "${SED_EXTENDED_REGEX_FLAG}" \
            -e 's|'"${RESPONSE_PATTERN}"'|twiping: got response from \2 after '"${ELAPSED_TIME}"' seconds: \5|g'
        break
    fi

    # Pause and try again
    "${SLEEP}" "${SLEEP_INTERVAL}"
done

